// Copyright 2016-2022 The Libsacloud Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"log"
	"path/filepath"

	"github.com/sacloud/libsacloud/v2/internal/define"
	"github.com/sacloud/libsacloud/v2/internal/dsl"
	"github.com/sacloud/libsacloud/v2/internal/tools"
)

const (
	apisDestination = "sacloud/fake/zz_api_ops.go"
	opsDestination  = "sacloud/fake/ops_%s.go"
	testDestination = "sacloud/fake/zz_api_ops_test.go"
)

func init() {
	log.SetFlags(0)
	log.SetPrefix("gen-api-fake-op: ")
}

func main() {
	// generate xxxOp
	outputPath := apisDestination
	tools.WriteFileWithTemplate(&tools.TemplateConfig{
		OutputPath: filepath.Join(tools.ProjectRootPath(), outputPath),
		Template:   apisTmpl,
		Parameter:  define.APIs,
	})
	log.Printf("generated: %s\n", outputPath)

	// generate funcs
	dsl.IsOutOfSacloudPackage = true
	for _, resource := range define.APIs {
		dest := fmt.Sprintf(opsDestination, resource.FileSafeName())
		wrote := tools.WriteFileWithTemplate(&tools.TemplateConfig{
			OutputPath:         filepath.Join(tools.ProjectRootPath(), dest),
			Template:           opsTmpl,
			Parameter:          resource,
			PreventOverwriting: true,
		})
		if wrote {
			log.Printf("generated: %s\n", filepath.Join(dest))
		}
	}

	// generate xxxOp
	outputPath = testDestination
	tools.WriteFileWithTemplate(&tools.TemplateConfig{
		OutputPath: filepath.Join(tools.ProjectRootPath(), outputPath),
		Template:   testTmpl,
		Parameter:  define.APIs,
	})
	log.Printf("generated: %s\n", outputPath)
}

const apisTmpl = `// generated by 'github.com/sacloud/libsacloud/internal/tools/gen-api-fake-op'; DO NOT EDIT

package fake

import (
{{- range .ImportStatements "sync" "github.com/sacloud/libsacloud/v2/sacloud" "github.com/sacloud/libsacloud/v2/sacloud/types"}}
	{{ . }}
{{- end }}
)

var switchOnce sync.Once

// SwitchFactoryFuncToFake switches sacloud.xxxAPI's factory methods to use fake client
func SwitchFactoryFuncToFake() {
	switchOnce.Do(func(){
		switchFactoryFuncToFake()
	})
}

func switchFactoryFuncToFake() {
{{ range . -}}
	sacloud.SetClientFactoryFunc(Resource{{.TypeName}}, func(caller sacloud.APICaller) interface{} {
		return New{{ .TypeName }}Op()
	})
{{ end -}}
}


{{ range . }}{{ $typeName := .TypeName}} 

/************************************************* 
* {{$typeName}}Op
*************************************************/

// {{ .TypeName }}Op is fake implementation of {{ .TypeName }}API interface
type {{ .TypeName }}Op struct{
	key string
}

// New{{ $typeName}}Op creates new {{ $typeName}}Op instance
func New{{ $typeName}}Op() sacloud.{{ $typeName}}API {
	return &{{$typeName}}Op {
		key: Resource{{$typeName}},
	}
}
{{ end -}}
`

const opsTmpl = `package fake

import (
{{- range .ImportStatements "context" "github.com/sacloud/libsacloud/v2/sacloud" "github.com/sacloud/libsacloud/v2/sacloud/types"}}
	{{ . }}
{{- end }}
)

{{ range .Operations }}
// {{ .MethodName }} is fake implementation
func (o *{{ $.TypeName }}Op) {{ .MethodName }}(ctx context.Context{{if not $.IsGlobal}}, zone string{{end}}{{ range .Arguments }}, {{ .ArgName }} {{ .TypeName }}{{ end }}) {{.ResultsStatement}} {
{{ if eq .MethodName "Find" -}}
	results, _ := find(o.key, {{if $.IsGlobal}}sacloud.APIDefaultZone{{else}}zone{{end}}, conditions)
	var values []*sacloud.{{$.TypeName}}
	for _, res := range results {
		dest := &sacloud.{{$.TypeName}}{}
		copySameNameField(res, dest)
		values = append(values, dest)
	}
	return &sacloud.{{.ResultTypeName}}{
		Total:    len(results),
		Count:    len(results),
		From:     0,
		{{$.TypeName}}: values,
	}, nil
{{ else if eq .MethodName "List" -}}
	results, _ := find(o.key, {{if $.IsGlobal}}sacloud.APIDefaultZone{{else}}zone{{end}}, nil)
	var values []*sacloud.{{$.TypeName}}
	for _, res := range results {
		dest := &sacloud.{{$.TypeName}}{}
		copySameNameField(res, dest)
		values = append(values, dest)
	}
	return &sacloud.{{.ResultTypeName}}{
		Total:    len(results),
		Count:    len(results),
		From:     0,
		{{$.TypeName}}: values,
	}, nil
{{ else if eq .MethodName "Create" -}}
	result := &sacloud.{{$.TypeName}}{}
	copySameNameField(param, result)
	fill(result, fillID, fillCreatedAt)

	// TODO core logic is not implemented

	s.set{{$.TypeName}}({{if $.IsGlobal}}sacloud.APIDefaultZone{{else}}zone{{end}}, result)
	return result, nil
{{ else if eq .MethodName "Read" -}}
	value := s.get{{$.TypeName}}ByID({{if $.IsGlobal}}sacloud.APIDefaultZone{{else}}zone{{end}}, id)
	if value == nil {
		return nil, newErrorNotFound(o.key, id)
	}
	dest := &sacloud.{{$.TypeName}}{}
	copySameNameField(value, dest)
	return dest, nil
{{ else if eq .MethodName "Update" -}}
	value, err := o.Read(ctx{{if not $.IsGlobal}}, zone{{end}}, id)
	if err != nil {
		return nil, err
	}
	copySameNameField(param, value)
	fill(value, fillModifiedAt)

	// TODO core logic is not implemented
	return value, nil
{{ else if eq .MethodName "Delete" -}}
	_, err := o.Read(ctx{{if not $.IsGlobal}}, zone{{end}}, id)
	if err != nil {
		return err
	}

	// TODO core logic is not implemented

	s.delete(o.key, {{if $.IsGlobal}}sacloud.APIDefaultZone{{else}}zone{{end}}, id)
	return nil
{{ else -}}
	// TODO not implemented
	err := errors.New("not implements")
	return {{.ReturnErrorStatement}}
{{ end -}}
}
{{ end }}
`

const testTmpl = `// generated by 'github.com/sacloud/libsacloud/v2/internal/tools/gen-api-fake-op'; DO NOT EDIT

package fake

import (
{{- range .ImportStatements "testing" "github.com/sacloud/libsacloud/v2/sacloud" "github.com/sacloud/libsacloud/v2/sacloud/types"}}
        {{ . }}
{{- end }}
)

func TestResourceOps(t *testing.T) {
{{ range . }}
        if op, ok := New{{.TypeName}}Op().(sacloud.{{.TypeName}}API); !ok {
                t.Fatalf("%s is not sacloud.{{.TypeName}}", op)
        }
{{ end }}
}`
